﻿<template>
  <div class="app-container">
    <p>调整速度：<el-slider v-model="speed" style="width: 50%" :min="minSpeed" :max="maxSpeed" :marks="marks" @input="onSpeedChanged" /></p>
    <canvas id="canvas" width="350" height="450">
      您的浏览器不支持 HTML5！！！
    </canvas>
  </div>
</template>

<script>
import TetrisAI from './ai';

export default {
  name: 'Tetris',
  data() {
    return {
      minSpeed: 100,
      maxSpeed: 5000,
      marks: { 1: '快', 500: '慢' },
      // 画布
      canvas: null,
      graphic: null,
      GRAPHIC_WIDTH: 330, // 画布长度（像素）
      GRAPHIC_HEIGHT: 420, // 画布高度（像素）
      TETRIS_MAP_WIDTH_MAX: 10, // 横向格子数
      TETRIS_MAP_HEIGHT_MAX: 20, // 竖向格子数
      TETRIS_COLORS: [
        'white', 'gold', 'blue', 'purple', 'red',
        'slategray', 'green', 'chocolate', 'black', 'pink'
      ],

      // 方块
      TETRIS_SIZE: 20, // 一个格子大小，一个方块由多个格子组成
      TETRIS_TYPES: [0, 1, 2, 3, 4, 5, 6], // 7 种方块
      TETRIS_MAP: null, // 当前布局
      TETRIS_BLOCK: [ // 不同类型方块在 4 种旋转方位上的定义
        /* type = 0
        |
        */
        [ -1, 0, 0, 0, 1, 0, 2, 0 ],
        [ 0, -2, 0, -1, 0, 0, 0, 1 ],
        [ -1, 0, 0, 0, 1, 0, 2, 0 ],
        [ 0, -2, 0, -1, 0, 0, 0, 1 ],
        /* type = 1
        ::
        */
        [ 0, -1, 1, -1, 0, 0, 1, 0 ],
        [ 0, -1, 1, -1, 0, 0, 1, 0 ],
        [ 0, -1, 1, -1, 0, 0, 1, 0 ],
        [ 0, -1, 1, -1, 0, 0, 1, 0 ],
        /* type = 2
        | 0    00  0
        | 00  00   00    00
        |  0        0   00
        */
        [ 1, -1, 0, 0, 1, 0, 0, 1 ],
        [ -1, 0, 0, 0, 0, 1, 1, 1 ],
        [ 1, -1, 0, 0, 1, 0, 0, 1 ],
        [ -1, 0, 0, 0, 0, 1, 1, 1 ],
        /* type = 3
        |  0        0   00
        | 00  00   00    00
        | 0    00  0
        */
        [ -1, -1, -1, 0, 0, 0, 0, 1 ],
        [ 0, 0, 1, 0, -1, 1, 0, 1 ],
        [ -1, -1, -1, 0, 0, 0, 0, 1 ],
        [ 0, 0, 1, 0, -1, 1, 0, 1 ],
        /* type = 4
        |   0 00      0
        | 000  0 000  0
        |      0 0    00
        */
        [ -1, 0, 0, 0, 1, 0, 1, 1 ],
        [ 0, -1, 0, 0, -1, 1, 0, 1 ],
        [ -1, -1, -1, 0, 0, 0, 1, 0 ],
        [ 0, -1, 1, -1, 0, 0, 0, 1 ],
        /* type = 5
        |  0    0      00
        |  000  0 000  0
        |      00   0  0
        */
        [ -1, 0, 0, 0, 1, 0, -1, 1 ],
        [ -1, -1, 0, -1, 0, 0, 0, 1 ],
        [ 1, -1, -1, 0, 0, 0, 1, 0 ],
        [ 0, -1, 0, 0, 0, 1, 1, 1 ],
        /* type = 6
        | 0   0      0
        |000 00 000  00
        |     0  0   0
        */
        [ -1, 0, 0, 0, 1, 0, 0, 1 ],
        [ 0, -1, -1, 0, 0, 0, 0, 1 ],
        [ 0, -1, -1, 0, 0, 0, 1, 0 ],
        [ 0, -1, 0, 0, 1, 0, 0, 1 ]
      ],

      // AI
      TETRIS_ANGLE_MAX: 4,
      TETRIS_DOTS_MAX: 4,
      TETRIS_QUEUE_MAX: 6,

      // 定时器
      interval: null,
      speed: 1, // 越小越快

      // 属性
      cur_type: 0, // 当前方块类型
      next_type: 0, // 下一个方块类型
      clears: 0,
      tetris_count: 0,
      clear_lines: [ 0, 0, 0, 0, 0 ],
      combo_time: 0,
      high_combo: 0,
      combo_times: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
    };
  },
  mounted() {
    this.init();

    this.interval = setInterval(() => { this.update(); }, this.speed);
  },
  beforeDestroy() {
    clearInterval(this.interval);
  },
  methods: {
    // 调整速度
    onSpeedChanged() {
      clearInterval(this.interval);

      this.interval = setInterval(() => { this.update(); }, this.speed);
    },
    // 生成随机方块类型
    get_next_tetris() {
      return this.TETRIS_TYPES[Math.floor(Math.random() * this.TETRIS_TYPES.length)];
    },
    // 初始化
    init() {
      // 初始化 AI
      TetrisAI.init(
        this.TETRIS_MAP_WIDTH_MAX,
        this.TETRIS_MAP_HEIGHT_MAX,
        this.TETRIS_ANGLE_MAX,
        this.TETRIS_DOTS_MAX,
        this.TETRIS_BLOCK);

      // 初始化画布
      this.canvas = document.getElementById('canvas');
      this.graphic = this.canvas.getContext('2d');

      // 初始化当前和下一个方块类型
      this.cur_type = this.get_next_tetris();
      this.next_type = this.get_next_tetris();

      // 初始化布局为全盘空白
      this.TETRIS_MAP = new Array(this.TETRIS_MAP_HEIGHT_MAX + this.TETRIS_DOTS_MAX - 1);
      for (let y = 0; y < this.TETRIS_MAP_HEIGHT_MAX + this.TETRIS_DOTS_MAX - 1; y++) {
        this.TETRIS_MAP[y] = new Array(this.TETRIS_MAP_WIDTH_MAX);
        for (let x = 0; x < this.TETRIS_MAP_WIDTH_MAX; x++) {
          this.TETRIS_MAP[y][x] = 0;
        }
      }
    },
    // 方块摆放逻辑处理
    game_logic() {
      this.cur_type = this.next_type;
      this.next_type = this.get_next_tetris();

      const best = TetrisAI.getBestPos(this.combo_time, this.TETRIS_MAP, [ this.cur_type, this.next_type ], 0);
      // debugger
      // console.log(best.m_score);
      // document.write(best.m_pos.x, '|', best.m_pos.y);
      if (!best.m_pos) {
        this.$message.error('Game over 1!');
        clearInterval(this.interval);
        return;
      } else {
        this.tetris_count++;

        const t_pos = this.TETRIS_BLOCK[this.cur_type * this.TETRIS_ANGLE_MAX + best.m_pos.angle];
        for (let n = 0; n < this.TETRIS_DOTS_MAX * 2; n += 2) {
          const w = best.m_pos.x + t_pos[n];
          const h = best.m_pos.y + t_pos[n + 1];
          this.TETRIS_MAP[h][w] = this.cur_type + 1;
        }

        let _y = 0;
        let lines = 0;
        for (let y = 0; y < this.TETRIS_MAP_HEIGHT_MAX + this.TETRIS_DOTS_MAX - 1; y++) {
          let is_full = true;
          for (let x = 0; x < this.TETRIS_MAP_WIDTH_MAX; x++) {
            this.TETRIS_MAP[_y][x] = this.TETRIS_MAP[y][x];
            if (this.TETRIS_MAP[y][x] === 0) {
              is_full = false;
            }
          }

          if (is_full) {
            lines++;
          } else {
            _y++;
          }
        }

        for (var y = _y; y < this.TETRIS_MAP_HEIGHT_MAX + this.TETRIS_DOTS_MAX - 1; y++) {
          for (var x = 0; x < this.TETRIS_MAP_WIDTH_MAX; x++) {
            this.TETRIS_MAP[y][x] = 0;
          }
        }

        this.clears += lines;
        if (lines > 1) {
          this.clear_lines[lines]++;

          this.combo_time++;
          if (this.high_combo < this.combo_time) {
            this.high_combo = this.combo_time;
          }

          if (this.combo_time > 2) {
            this.combo_times[this.combo_time]++;
          }
        } else {
          this.combo_time = 0;
        }

        for (let x = 0; x < this.TETRIS_MAP_WIDTH_MAX; x++) {
          let is_empty = true;
          for (let y = this.TETRIS_MAP_HEIGHT_MAX; y < this.TETRIS_MAP_HEIGHT_MAX + this.TETRIS_DOTS_MAX - 1; y++) {
            if (this.TETRIS_MAP[y][x] !== 0) {
              is_empty = false;
              break;
            }
          }

          if (!is_empty) {
            this.$message.error('Game over 2!');
            clearInterval(this.interval);
            return;
          }
        }
      }
    },
    // 画出边框
    draw_frame() {
      this.graphic.fillStyle = 'white';
      this.graphic.fillRect(1, 1, this.canvas.width, this.canvas.height);

      this.graphic.strokeStyle = 'black';
      this.graphic.strokeRect(1, 1, this.GRAPHIC_WIDTH - 2, this.GRAPHIC_HEIGHT - 1);

      this.graphic.strokeStyle = 'black';
      this.graphic.strokeRect(10, 10, this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + 1, this.TETRIS_SIZE * this.TETRIS_MAP_HEIGHT_MAX + 1);

      this.graphic.strokeStyle = 'black';
      this.graphic.strokeRect(this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + 20,
        10,
        this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 1),
        this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2));

      this.graphic.strokeStyle = 'black';
      this.graphic.strokeRect(this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + 20,
        this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2) + 20,
        this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 1),
        this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2));

      this.graphic.strokeStyle = 'black';
      this.graphic.strokeRect(this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + 20,
        this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2) * 2 + 30,
        this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 1),
        this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 3));

      this.graphic.font = '14px Courier New';
      this.graphic.fillStyle = 'black';

      const dqk = '当前块';
      const xyk = '下一块';
      const fks = '方块数';
      const xch = '消除行';
      const lxs = '连消数';
      this.graphic.fillText(dqk, this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + (this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 3) - this.graphic.measureText(dqk).width) / 2, 30);
      this.graphic.fillText(xyk, this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + (this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 3) - this.graphic.measureText(xyk).width) / 2, this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2) + 40);
      this.graphic.fillText(fks, this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + (this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 3) - this.graphic.measureText(fks).width) / 2, this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2) * 2 + 50);
      this.graphic.fillText(xch, this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + (this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 3) - this.graphic.measureText(xch).width) / 2, this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2) * 2 + 95);
      this.graphic.fillText(lxs, this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + (this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 3) - this.graphic.measureText(lxs).width) / 2, this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2) * 2 + 140);

      this.graphic.font = '22px Courier New';
      this.graphic.fillStyle = 'red';
      this.graphic.fillText(this.tetris_count, this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + (this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 3) - this.graphic.measureText(this.tetris_count).width) / 2,
        this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2) * 2 + 70);
      this.graphic.fillText(this.clears, this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + (this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 3) - this.graphic.measureText(this.clears).width) / 2,
        this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2) * 2 + 115);
      this.graphic.fillText(this.combo_time, this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + (this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 3) - this.graphic.measureText(this.combo_time).width) / 2,
        this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2) * 2 + 160);

      this.graphic.fillStyle = this.TETRIS_COLORS[this.cur_type + 1];
      for (let n = 0; n < this.TETRIS_DOTS_MAX * 2; n += 2) {
        this.graphic.fillRect(this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 1) / 2 + this.TETRIS_SIZE * this.TETRIS_BLOCK[this.cur_type * this.TETRIS_ANGLE_MAX][n],
          this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2) / 2 + this.TETRIS_SIZE * -this.TETRIS_BLOCK[this.cur_type * this.TETRIS_ANGLE_MAX][n + 1],
          this.TETRIS_SIZE, this.TETRIS_SIZE);
      }

      this.graphic.fillStyle = this.TETRIS_COLORS[this.next_type + 1];
      for (let n = 0; n < this.TETRIS_DOTS_MAX * 2; n += 2) {
        this.graphic.fillRect(this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 1) / 2 + this.TETRIS_SIZE * this.TETRIS_BLOCK[this.next_type * this.TETRIS_ANGLE_MAX][n],
          this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2) / 2 + this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2) + 10 + this.TETRIS_SIZE * -this.TETRIS_BLOCK[this.next_type * this.TETRIS_ANGLE_MAX][n + 1],
          this.TETRIS_SIZE, this.TETRIS_SIZE);
      }

      this.graphic.strokeStyle = 'white';
      for (let y = 0; y < this.TETRIS_DOTS_MAX; y++) {
        for (let x = 0; x < this.TETRIS_DOTS_MAX; x++) {
          this.graphic.strokeRect(this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + this.TETRIS_SIZE * 1.5 + this.TETRIS_SIZE * x,
            this.TETRIS_SIZE * y + 40, this.TETRIS_SIZE, this.TETRIS_SIZE);
          this.graphic.strokeRect(this.TETRIS_SIZE * this.TETRIS_MAP_WIDTH_MAX + this.TETRIS_SIZE * 1.5 + this.TETRIS_SIZE * x,
            this.TETRIS_SIZE * y + this.TETRIS_SIZE * (this.TETRIS_DOTS_MAX + 2) + 50, this.TETRIS_SIZE, this.TETRIS_SIZE);
        }
      }
    },
    // 画出布局
    draw_map() {
      for (let x = 0; x < this.TETRIS_MAP_WIDTH_MAX; x++) {
        for (let y = 0; y < this.TETRIS_MAP_HEIGHT_MAX; y++) {
          const h = this.TETRIS_MAP_HEIGHT_MAX - y;
          switch (this.TETRIS_MAP[y][x]) {
            case 0:
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
              this.graphic.fillStyle = this.TETRIS_COLORS[this.TETRIS_MAP[y][x]];
              this.graphic.fillRect(1 + this.TETRIS_SIZE * x + 10, 1 + this.TETRIS_SIZE * h - 10, this.TETRIS_SIZE, this.TETRIS_SIZE);
              break;

            default:
              this.graphic.fillStyle = this.TETRIS_COLORS[9];
              this.graphic.fillRect(1 + this.TETRIS_SIZE * x + 10, 1 + this.TETRIS_SIZE * h - 10, this.TETRIS_SIZE, this.TETRIS_SIZE);
              break;
          }

          this.graphic.strokeStyle = 'white';
          this.graphic.strokeRect(1 + this.TETRIS_SIZE * x + 10, 1 + this.TETRIS_SIZE * h - 10, this.TETRIS_SIZE - 1, this.TETRIS_SIZE - 1);
        }
      }
    },
    // 定时刷新画面
    update() {
      this.game_logic();
      this.draw_frame();
      this.draw_map();
    }
  },
};
</script>
